package com.navinfo.data.journal;

import org.apache.commons.csv.CSVFormat;
import org.apache.commons.csv.CSVRecord;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.FileNotFoundException;
import java.io.FileReader;
import java.io.IOException;
import java.io.Reader;
import java.util.*;
import java.util.stream.Collectors;

/**
 * 车辆趟程发现概括版V3，及其平均运程计算
 * Model：去掉static方法
 * 1.利用vertex的起止时间点进行趟程切分，不采用dfs算法
 * 2.进行趟程节点循环相同趟程的key合并
 * 3.使用瓦片/城市ID来jvs节点表
 * 4.尝试去掉趟程中短趟程（GPS偏移，大于LOAD_DISTANCE的行程才进行趟程计算）
 * 5.调整points序列String规则：以最小瓦片id开始
 */
public class VehicleJournalModelV4 {
    private static final Logger LOGGER = LoggerFactory.getLogger(VehicleJournalModelV4.class);

    /**
     * 运距阀值(米)
     */
    private static int LOAD_DISTANCE = 30*1000;
    /**
     * 瓦片级别
     */
    private static int ZOOM = 12;
    /**
     * JournalVertex集合
     */
    private List<JournalVertex> jvs = new ArrayList<JournalVertex>();
    /**
     * JournalEdge集合
     */
    private Map<String, JournalEdge> jes = new HashMap<String, JournalEdge>();

    /**
     * 根据经纬度获取瓦片ID
     *
     * @param lat 经度
     * @param lon 纬度
     * @return 瓦片ID
     */
    public long getLc(int lat, int lon) {
        Long tileIdMin = VehiclePassTimesQuadtree.getTileNumber(lat, lon, ZOOM);

        return tileIdMin;
    }

    /**
     * add or find SimilarVertex
     *
     * @param lat2 经度
     * @param lng2 维度
     * @param time 时间点
     * @param flag 0开始时间点，1结束时间点
     * @return list下标
     */
    private long afSimilarVertex(double lat2, double lng2, int time, int flag) {
        int result = -1;
        long id1 = getLc((int) (lat2 * 1000000), (int) (lng2 * 1000000));
        for (int i = 0; i < jvs.size(); i++) {
            JournalVertex jv = jvs.get(i);
            long id2 = jv.getTileNumber();
            if (id1 == id2) {
                jv.getTimelist().add(time + ":" + flag);
                jvs.set(i, jv);
                result = i;
                break;
            }
        }
        if (result == -1) {
            JournalVertex jv = new JournalVertex(id1);
            jv.getTimelist().add(time + ":" + flag);
            jv.setLat(lat2);
            jv.setLng(lng2);
            jvs.add(jv);
            result = jvs.size() - 1;
        }

        return id1;
    }

    /**
     * add or update JournalEdge
     */
    private void afJournalEdge(long src, long dst, int totalDistance, int routeStartTime, int routeEndTime,int vehicleEstimatedLoad) {
        String mapkey = src + "-" + dst;
        if (jes.containsKey(mapkey)) {
            JournalEdge je = jes.get(mapkey);
            je.setTimes(je.getTimes() + 1);
            je.setTotalDistance(je.getTotalDistance() + totalDistance);
            je.setTotalVehicleEstimatedLoad(je.getTotalVehicleEstimatedLoad()+vehicleEstimatedLoad);
            je.setTotalDuration(je.getTotalDuration() + (routeEndTime - routeStartTime));
            jes.put(mapkey, je);
        } else {
            JournalEdge je = new JournalEdge(src, dst, totalDistance, 1, routeEndTime - routeStartTime,vehicleEstimatedLoad);
            jes.put(mapkey, je);
        }
    }

    /**
     * 使用 Map按key进行排序
     *
     * @param map
     * @return
     */
    public Map<String, JournalDescV3> sortMapByKey(Map<String, JournalDescV3> map) {
        if (map == null || map.isEmpty()) {
            return new HashMap<String,JournalDescV3>();
        }

        Map<String, JournalDescV3> sortMap = new TreeMap<String, JournalDescV3>(new MapKeyComparator());
        sortMap.putAll(map);

        return sortMap;
    }

    /**
     * 判断是否存在已有的mapkey，如果存在返回map中的key，否则返回null
     *
     * @param journalDescV3Map
     * @param points
     * @return mapkey
     */
    private String getSameKeyFromMap(Map<String, JournalDescV3> journalDescV3Map, String points) {
        LOGGER.debug("---------------s------------------------");
        LOGGER.debug(points);
        LOGGER.debug("---------------m------------------------");
        for (Map.Entry<String, JournalDescV3> entry : journalDescV3Map.entrySet()) {
            LOGGER.debug("Key = " + entry.getKey() + ", Value = " + entry.getValue().toString());
        }
        LOGGER.debug("---------------e------------------------");
        String result = null;
        String[] pointArray = points.split("->");
        String[] newPointArray;
        newPointArray = Arrays.copyOfRange(pointArray, 0, pointArray.length - 1);
        LOGGER.debug("left:" + Arrays.toString(newPointArray));

        OUT:
        for (Map.Entry<String, JournalDescV3> entry : journalDescV3Map.entrySet()) {
//            LOGGER.info("Key = " + entry.getKey() + ", Value = " + entry.getValue().toString());
            String[] data = entry.getKey().split("->");
            if (pointArray.length != data.length) {
                continue;
            }
            String[] newData;
            newData = Arrays.copyOfRange(data, 0, data.length - 1);
            List list = new ArrayList(Arrays.asList(newData));
            for (int i = 0; i < list.size(); i++) {
                for (int j = 0; j < i; j++) {
                    String tmp = (String) list.get(0);
                    list.remove(0);
                    list.add(tmp);
                }
                Object[] c = list.toArray();
                LOGGER.debug("right:" + Arrays.toString(c));
                if (Arrays.equals(newPointArray, c)) {
                    result = entry.getKey();
                    break OUT;
                }
            }
        }
        LOGGER.debug("result:" + result);
        return result;
    }

    /**
     * 调整points序列String规则：以最小瓦片id开始
     *
     * @param points points序列String
     */
    private String transformPoints(String points) {
        String result= null;
        String[] pointArray = points.split("->");
        String[] newPointArray;
        newPointArray = Arrays.copyOfRange(pointArray, 0, pointArray.length - 1);

        List list = new ArrayList(Arrays.asList(newPointArray));
        long smallestPoint = Long.MAX_VALUE;
        for (int i = 0; i < list.size(); i++) {
            if (Long.parseLong((String) list.get(i))<smallestPoint) {
                smallestPoint = Long.parseLong((String) list.get(i));
            }
        }
        OUT2:
        for (int i = 0; i < list.size(); i++) {
            if (Long.parseLong((String) list.get(0))==smallestPoint) {
                break OUT2;
            }
            for (int j = 0; j < i; j++) {
                String tmp = (String) list.get(0);
                list.remove(0);
                list.add(tmp);
            }
        }

        list.add(String.valueOf(smallestPoint));
        result = StringUtils.join(list.toArray(), "->");
        LOGGER.debug(smallestPoint+"result:" + result);

        return result;
    }

    /**
     * 将趟程（各类节点顺序组合）放入journalDescV3Map
     *
     * @param journalDescV3Map
     * @param points
     * @param totalDistance
     * @param totalDuration
     */
    private void handleJournalDescV3(Map<String, JournalDescV3> journalDescV3Map, String points, int totalDistance, int totalDuration) {
        LOGGER.debug("totalDuration:" + totalDuration);
//            大于LOAD_DISTANCE的行程才进行趟程计算
        if (totalDistance <= LOAD_DISTANCE) {
            return;
        }

        String result = getSameKeyFromMap(journalDescV3Map, points);
        if (result != null) {
            JournalDescV3 jdv3 = journalDescV3Map.get(result);
            jdv3.setTcTimes(jdv3.getTcTimes() + 1);
            jdv3.setTotalDistance(jdv3.getTotalDistance() + totalDistance);
            jdv3.setTotalDuration(jdv3.getTotalDuration() + totalDuration);
            journalDescV3Map.put(result, jdv3);
        } else {
            JournalDescV3 jdv3 = new JournalDescV3(transformPoints(points), totalDuration, totalDistance, 1);
            journalDescV3Map.put(points, jdv3);
        }
    }

    /**
     * 依据行程记录生成趟程记录
     *
     * @param tid 车辆TID
     * @param tripRawRecordList 行程记录
     * @return 趟程记录BEAN
     */
    public VehicleJournalBean  generateJournal(String tid, List<TripRawRecord> tripRawRecordList) {
        VehicleJournalBean vehicleJournalBean = new VehicleJournalBean();
        tripRawRecordList.sort(Comparator.comparingInt(TripRawRecord::getRouteStartTime));
        List<TripRecord> tripRecordList = new ArrayList<TripRecord>();
        for (TripRawRecord record : tripRawRecordList) {
            double routeStartLatitude = ((double)record.getRouteStartLatitude()) / 1000000;
            double routeStartLongitude = ((double)record.getRouteStartLongitude())/1000000;
            double routeEndLatitude = ((double)record.getRouteEndLatitude()) / 1000000;
            double routeEndLongitude = ((double)record.getRouteEndLongitude()) / 1000000;
            int routeIntegralMileage = (record.getRouteIntegralMileage());
            int routeStartTime = (record.getRouteStartTime());
            int routeEndTime = (record.getRouteEndTime());

            int vehicleEstimatedLoad = (record.getVehicleEstimatedLoad());

            long src = afSimilarVertex(routeStartLatitude, routeStartLongitude, routeStartTime, 0);
            long dst = afSimilarVertex(routeEndLatitude, routeEndLongitude, routeEndTime, 1);
            afJournalEdge(src, dst, routeIntegralMileage, routeStartTime, routeEndTime,vehicleEstimatedLoad);

            tripRecordList.add(new TripRecord(src, dst, routeIntegralMileage, routeStartTime, routeEndTime));
        }
        //read trip data end------------------------

        LOGGER.debug("-----------打印所有行程记录--------------");
        for (int j = 0; j < tripRecordList.size(); j++) {
            LOGGER.debug(tripRecordList.get(j).toString());
        }

        LOGGER.debug("-----------打印所有趟程停靠点--------------");
        Map<String, JournalDescV3> journalDescV3Map = new HashMap<String, JournalDescV3>();
        int num = 0;
        long lastNum = -1;//上一点标号
        for (JournalVertex jv : jvs) {
            LOGGER.debug("Number:" + num + ", " + jv.toString());
            long currentNode = jv.getTileNumber();

            List<String> timelist = jv.getTimelist();
            int totalDuration = 0;
            int totalDistance = 0;
//            int tcTimes = 0;//趟程次数
            int durationFlag = 0;//趟程标志
            int computeFlag = 0;//计算标志
            int durationStartTime = 0;
            int durationEndTime = 0;
            StringBuffer points = new StringBuffer();
            for (int i = 0; i < timelist.size(); i++) {
                if (durationFlag == 0) {//尚无趟程
                    if (Integer.parseInt(timelist.get(i).split(":")[1]) == 0) {//判断是起始时间点
                        durationStartTime = Integer.parseInt(timelist.get(i).split(":")[0]);
                        durationFlag = 1;
                    }
                }
                if (durationFlag == 1) {//存在趟程
                    if (Integer.parseInt(timelist.get(i).split(":")[1]) == 1) {//判断是结束时间点
                        durationEndTime = Integer.parseInt(timelist.get(i).split(":")[0]);
                        durationFlag = 2;
//                        tcTimes++;//趟程次数
                    }
                }

                if (durationFlag == 2) {
                    //存在趟程可以开始计算运程和时长
                    LOGGER.debug("durationStartTime:" + durationStartTime + "durationEndTime:" + durationEndTime);
                    totalDuration = durationEndTime - durationStartTime;
                    for (int j = 0; j < tripRecordList.size(); j++) {
                        TripRecord tr = tripRecordList.get(j);

                        if ((currentNode == tr.getSrc()) && (durationStartTime == tr.getRouteStartTime())) {
                            computeFlag = 1;
                        }

                        if (computeFlag == 1) {
                            if (lastNum == -1) {
                                points.append(tr.getSrc()).append("->");
                                lastNum = tr.getSrc();
                            } else {
                                if (lastNum != tr.getSrc()) {
                                    points.append(tr.getSrc()).append("->");
                                    lastNum = tr.getSrc();
                                }
                            }


                            totalDistance = totalDistance + tr.getRouteIntegralMileage();

                            if (currentNode == tr.getDst()) {
                                if (durationEndTime == tr.getRouteEndTime()) {
                                    points.append(tr.getDst());
                                    handleJournalDescV3(journalDescV3Map, points.toString(), totalDistance, totalDuration);
                                    computeFlag = 0;
                                    durationFlag = 0;
                                    totalDuration = 0;
                                    totalDistance = 0;
                                    lastNum = -1;
                                    points = new StringBuffer();
                                    break;//跳出内循环
                                } else {
                                    //应该不存在这样情况
                                }
                            } else {
                                if (lastNum != tr.getDst()) {
                                    points.append(tr.getDst()).append("->");
                                    lastNum = tr.getDst();
                                }
                            }
                        }
                    }
                }
            }
            num++;
        }

        LOGGER.debug("-----------打印所有趟程停靠点间行程--------------");
        for (Map.Entry<String, JournalEdge> entry : jes.entrySet()) {
            LOGGER.debug("Key = " + entry.getKey() + ", Value = " + entry.getValue().toString());
        }

        LOGGER.debug("-----------打印所有趟程描述--------------");
        //Map<String,JournalDescV3> journalDescV3Map = new HashMap<String,JournalDescV3>();
        Map<String, JournalDescV3> journalDescV3MapSorted = sortMapByKey(journalDescV3Map);    //按Key进行排序
        for (Map.Entry<String, JournalDescV3> entry : journalDescV3MapSorted.entrySet()) {
//            LOGGER.info("Key = " + entry.getKey() + ", Value = " + entry.getValue().toString());
            LOGGER.debug(entry.getValue().toString());
        }

        LOGGER.debug("-----------输出该车辆的总体平均运程和时长--------------");
        int allTotalDistance = 0;
        int allTotalDuration = 0;
        for (Map.Entry<String, JournalDescV3> entry : journalDescV3MapSorted.entrySet()) {
            JournalDescV3 jdv3 = entry.getValue();
            allTotalDistance = allTotalDistance + jdv3.getTotalDistance();
            allTotalDuration = allTotalDuration + jdv3.getTotalDuration();
        }

        double avgJournalDistance = 0;
        double avgJournalDuration = 0;
        for (Map.Entry<String, JournalDescV3> entry : journalDescV3MapSorted.entrySet()) {
            JournalDescV3 jdv3 = entry.getValue();
            avgJournalDistance = avgJournalDistance + ((double) jdv3.getTotalDistance() / jdv3.getTcTimes()) * ((double) jdv3.getTotalDistance() / allTotalDistance);
        }
        LOGGER.debug("avgJournalDistance=" + (int) avgJournalDistance);

        for (Map.Entry<String, JournalDescV3> entry : journalDescV3MapSorted.entrySet()) {
            JournalDescV3 jdv3 = entry.getValue();
            avgJournalDuration = avgJournalDuration + ((double) jdv3.getTotalDuration() / jdv3.getTcTimes()) * ((double) jdv3.getTotalDuration() / allTotalDuration);
        }
        LOGGER.debug("avgJournalDuration=" + (int) avgJournalDuration);

        vehicleJournalBean.setAvgJournalDistance((int) avgJournalDistance);
        vehicleJournalBean.setAvgJournalDuration((int) avgJournalDuration);
        vehicleJournalBean.setJournalVertexList(jvs);

        List<JournalEdge> jesList = jes.values().stream().collect(Collectors.toList());
        vehicleJournalBean.setJournalEdgeList(jesList);

//        List<JournalDescV3> jdsList = journalDescV3MapSorted.values().stream().collect(Collectors.toList());
        List<JournalDescV3> jdsList = new ArrayList(journalDescV3MapSorted.values());
        vehicleJournalBean.setJournalDescList(jdsList);

        vehicleJournalBean.setTid(tid);

        return vehicleJournalBean;
    }



    public static void main(String[] args) {
        Reader in = null;
        Iterable<CSVRecord> records = null;
        String tid = null;
        //read trip data start------------------------
        try {
            //百事车辆
//            tid = "10019411330";
//            in = new FileReader("E:\\qingqidata\\10019411330\\10019411330驾驶行为分析(2018-11-07-00-00-00至2018-11-12-00-00-00).csv");
            //节油大赛车辆
//            tid = "10021042261";
//            in = new FileReader("E:\\qingqidata\\10021042261驾驶行为分析(2018-09-27-00-00-00至2018-09-29-00-00-00).csv");
            //随便找的一台车
//            tid = "10020435809";
//            in = new FileReader("E:\\qingqidata\\10020435809驾驶行为分析(2018-12-10-00-00-00至2018-12-12-00-00-00).csv");
            //随便找的一台of37车
//            tid = "10019410082";
//            in = new FileReader("E:\\qingqidata\\10019410082驾驶行为分析(2018-12-10-00-00-00至2018-12-12-00-00-00).csv");
            //随便找的一台of37车（14天发现闭环）
            tid = "14705564807";
            in = new FileReader("E:\\qingqidata\\14705564807\\14705564807驾驶行为分析(2018-12-04-00-00-00至2018-12-18-00-00-00).csv");
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            LOGGER.error("FileNotFoundException");
        }

        try {
            records = CSVFormat.RFC4180.withFirstRecordAsHeader().parse(in);
        } catch (IOException e) {
            e.printStackTrace();
            LOGGER.error("IOException");
        }

        List<TripRawRecord> tripRawRecordList = new ArrayList<TripRawRecord>();
        for (CSVRecord record : records) {
            long routeStartLatitude = Long.parseLong(record.get("routeStartLatitude"));
            long routeStartLongitude = Long.parseLong(record.get("routeStartLongitude"));
            long routeEndLatitude = Long.parseLong(record.get("routeEndLatitude"));
            long routeEndLongitude = Long.parseLong(record.get("routeEndLongitude"));
            int routeIntegralMileage = Integer.parseInt(record.get("routeIntegralMileage"));
            int routeStartTime = Integer.parseInt(record.get("routeStartTime"));
            int routeEndTime = Integer.parseInt(record.get("routeEndTime"));

            int vehicleEstimatedLoad = Integer.parseInt(record.get("vehicleEstimatedLoad"));

            tripRawRecordList.add(new TripRawRecord(routeStartLatitude, routeStartLongitude, routeEndLatitude, routeEndLongitude, routeIntegralMileage, routeStartTime, routeEndTime,vehicleEstimatedLoad));
        }
        //read trip data end------------------------

        //调用模型并生成趟程
        VehicleJournalModelV4 vehicleJournalModel = new VehicleJournalModelV4();
        VehicleJournalBean vehicleJournalBean = vehicleJournalModel.generateJournal(tid,tripRawRecordList);
        LOGGER.info(vehicleJournalBean.toString());
    }
}
